﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;

namespace ScrollPP.NetService
{
    public sealed class ServiceTaskScheduler
    {
        private volatile bool _shouldStopBackgroundThread;
        private Queue<ServiceTask> _tasks = new Queue<ServiceTask>();
        private Object _taskQueueLock = new Object();
        private EventWaitHandle _workerFinishedEvent;
        private EventLog _eventLog;

        public ServiceTaskScheduler(EventLog eventLog)
        {
            _eventLog = eventLog;
            ThreadPool.QueueUserWorkItem(WorkerProc);
        }

        public void Invoke(Action action)
        {
            Invoke(action, -1);
        }

        public void Invoke(Action action, int timeout)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (_shouldStopBackgroundThread)
                return;

            using(var waitHandle = new ManualResetEvent(false))
            {
                EnqueueServiceTask(new ServiceTask()
                {
                    Action = action,
                    WaitHandle = waitHandle
                });

                waitHandle.WaitOne();
            }
        }

        public void InvokeAsync(Action action)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (_shouldStopBackgroundThread)
                return;

            EnqueueServiceTask(new ServiceTask()
            {
                Action = action,
                WaitHandle = null
            });
        }

        private void EnqueueServiceTask(ServiceTask task)
        {
            lock(_taskQueueLock)
            {
                _tasks.Enqueue(task);
                Monitor.PulseAll(_taskQueueLock);
            }
        }

        private void WorkerProc(object o)
        {
            _workerFinishedEvent = new ManualResetEvent(false);

            while(_shouldStopBackgroundThread == false || _tasks.Count > 0)
            {
                lock (_taskQueueLock)
                {
                    if(_tasks.Count == 0)
                        Monitor.Wait(_taskQueueLock);

                    if(_tasks.Count > 0)
                    {
                        ServiceTask task = _tasks.Dequeue();
                        try
                        {
                            task.Run();
                        }
                        catch(Exception ex)
                        {
                            if (_eventLog != null)
                                _eventLog.WriteEntry("Unknown exception in ServiceTaskScheduler: " + ex.ToString());
                        }
                    }
                }
            }

            _workerFinishedEvent.Set();
        }

        public void Stop()
        {
            Stop(-1);
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Stop(int timeout)
        {
            _shouldStopBackgroundThread = true;

            lock (_taskQueueLock)
            {
                Monitor.PulseAll(_taskQueueLock);
            }

            if(_workerFinishedEvent != null)
            {
                _workerFinishedEvent.WaitOne(timeout);
                _workerFinishedEvent.Close();
                _workerFinishedEvent = null;
            }
        }

        private sealed class ServiceTask
        {
            public EventWaitHandle WaitHandle { get; set; }

            public Action Action { get; set; }

            public void Run()
            {
                try
                {
                    Action.Invoke();
                }
                finally
                {
                    SetWaitHandle();
                }
            }

            private void SetWaitHandle()
            {
                if (WaitHandle != null)
                    WaitHandle.Set();
            }
        }
    }
}
